

<!DOCTYPE html>
<html lang="zh-CN" data-default-color-scheme=auto>



<head>
  <meta charset="UTF-8">
  <link rel="apple-touch-icon" sizes="76x76" href="/img/fluid.png">
  <link rel="icon" href="/img/favicon.ico">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0, shrink-to-fit=no">
  <meta http-equiv="x-ua-compatible" content="ie=edge">
  
  <meta name="theme-color" content="#2f4154">
  <meta name="author" content="xuehaoweng">
  <meta name="keywords" content="">
  
    <meta name="description" content="消息队列是重要的分布式系统组件，在高性能、高可用、低耦合等系统架构中扮演着重要作用。可用于异步通信、削峰填谷、解耦系统、数据缓存等多种业务场景。本文是关于消息队列（MQ）选型和常见问题的精心整理。在这篇文章中，我们将详细介绍消息队列的概念、作用以及如何选择适合自己需求的消息队列系统。  1、概述消息队列在分布式系统中扮演着至关重要的角色，它为系统架构提供了高性能、高可用性和低耦合的特性。通过利用">
<meta property="og:type" content="article">
<meta property="og:title" content="消息队列选型大战：如何在性能与可靠性之间找到最优解">
<meta property="og:url" content="http://blog.xhweng.cn/2025/02/11/%E5%A6%82%E4%BD%95%E9%80%89%E6%8B%A9%E5%90%88%E9%80%82%E7%9A%84%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97/index.html">
<meta property="og:site_name" content="有追求的开发者">
<meta property="og:description" content="消息队列是重要的分布式系统组件，在高性能、高可用、低耦合等系统架构中扮演着重要作用。可用于异步通信、削峰填谷、解耦系统、数据缓存等多种业务场景。本文是关于消息队列（MQ）选型和常见问题的精心整理。在这篇文章中，我们将详细介绍消息队列的概念、作用以及如何选择适合自己需求的消息队列系统。  1、概述消息队列在分布式系统中扮演着至关重要的角色，它为系统架构提供了高性能、高可用性和低耦合的特性。通过利用">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://files.mdnice.com/user/33721/bc3f86d9-adac-46a2-ac2f-11a9c3d5ef40.png">
<meta property="og:image" content="https://files.mdnice.com/user/33721/97a90923-4fb0-48c2-affb-be5d90d4097d.png">
<meta property="og:image" content="https://files.mdnice.com/user/33721/d15ef861-a9a8-4fd6-afa9-f6f6e7993ac1.png">
<meta property="og:image" content="https://files.mdnice.com/user/33721/919d003a-3c83-4002-9f0a-8a2376bd2719.png">
<meta property="og:image" content="https://files.mdnice.com/user/33721/cf0bfed8-7422-4194-a8b7-8f347255722e.png">
<meta property="og:image" content="https://files.mdnice.com/user/33721/a25caf99-64f2-40ff-a0f6-c3de925a24d9.png">
<meta property="og:image" content="https://files.mdnice.com/user/33721/55764f74-d6fc-4c45-83ee-2f2d6e861226.jpeg">
<meta property="article:published_time" content="2025-02-11T03:06:26.000Z">
<meta property="article:modified_time" content="2025-02-11T03:06:29.701Z">
<meta property="article:author" content="xuehaoweng">
<meta property="article:tag" content="框架">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:image" content="https://files.mdnice.com/user/33721/bc3f86d9-adac-46a2-ac2f-11a9c3d5ef40.png">
  
  
    <meta name="referrer" content="no-referrer-when-downgrade">
  
  
  <title>消息队列选型大战：如何在性能与可靠性之间找到最优解 - 有追求的开发者</title>

  <link  rel="stylesheet" href="https://lib.baomitu.com/twitter-bootstrap/4.6.1/css/bootstrap.min.css" />



  <link  rel="stylesheet" href="https://lib.baomitu.com/github-markdown-css/4.0.0/github-markdown.min.css" />

  <link  rel="stylesheet" href="https://lib.baomitu.com/hint.css/2.7.0/hint.min.css" />

  <link  rel="stylesheet" href="https://lib.baomitu.com/fancybox/3.5.7/jquery.fancybox.min.css" />



<!-- 主题依赖的图标库，不要自行修改 -->
<!-- Do not modify the link that theme dependent icons -->

<link rel="stylesheet" href="//at.alicdn.com/t/font_1749284_hj8rtnfg7um.css">



<link rel="stylesheet" href="//at.alicdn.com/t/font_1736178_lbnruvf0jn.css">


<link  rel="stylesheet" href="/css/main.css" />


  <link id="highlight-css" rel="stylesheet" href="/css/highlight.css" />
  
    <link id="highlight-css-dark" rel="stylesheet" href="/css/highlight-dark.css" />
  




  <script id="fluid-configs">
    var Fluid = window.Fluid || {};
    Fluid.ctx = Object.assign({}, Fluid.ctx)
    var CONFIG = {"hostname":"blog.xhweng.cn","root":"/","version":"1.9.7","typing":{"enable":true,"typeSpeed":70,"cursorChar":"_","loop":false,"scope":[]},"anchorjs":{"enable":true,"element":"h1,h2,h3,h4,h5,h6","placement":"left","visible":"hover","icon":""},"progressbar":{"enable":true,"height_px":3,"color":"#29d","options":{"showSpinner":false,"trickleSpeed":100}},"code_language":{"enable":true,"default":"TEXT"},"copy_btn":true,"image_caption":{"enable":true},"image_zoom":{"enable":true,"img_url_replace":["",""]},"toc":{"enable":true,"placement":"right","headingSelector":"h1,h2,h3,h4,h5,h6","collapseDepth":0},"lazyload":{"enable":true,"loading_img":"/img/loading.gif","onlypost":false,"offset_factor":2},"web_analytics":{"enable":true,"follow_dnt":true,"baidu":"b3bbb822b3d2a5e2761107c03cf72b8c","google":{"measurement_id":null},"tencent":{"sid":null,"cid":null},"woyaola":null,"cnzz":null,"leancloud":{"app_id":null,"app_key":null,"server_url":null,"path":"window.location.pathname","ignore_local":false}},"search_path":"/local-search.xml","include_content_in_search":true};

    if (CONFIG.web_analytics.follow_dnt) {
      var dntVal = navigator.doNotTrack || window.doNotTrack || navigator.msDoNotTrack;
      Fluid.ctx.dnt = dntVal && (dntVal.startsWith('1') || dntVal.startsWith('yes') || dntVal.startsWith('on'));
    }
  </script>
  <script  src="/js/utils.js" ></script>
  <script  src="/js/color-schema.js" ></script>
  

  
    <!-- Baidu Analytics -->
    <script async>
      if (!Fluid.ctx.dnt) {
        var _hmt = _hmt || [];
        (function() {
          var hm = document.createElement("script");
          hm.src = "https://hm.baidu.com/hm.js?b3bbb822b3d2a5e2761107c03cf72b8c";
          var s = document.getElementsByTagName("script")[0];
          s.parentNode.insertBefore(hm, s);
        })();
      }
    </script>
  

  
    <!-- Google tag (gtag.js) -->
    <script async>
      if (!Fluid.ctx.dnt) {
        Fluid.utils.createScript("https://www.googletagmanager.com/gtag/js?id=", function() {
          window.dataLayer = window.dataLayer || [];
          function gtag() {
            dataLayer.push(arguments);
          }
          gtag('js', new Date());
          gtag('config', '');
        });
      }
    </script>
  

  

  

  

  



  
<meta name="generator" content="Hexo 7.0.0"></head>


<body>
  

  <header>
    

<div class="header-inner" style="height: 70vh;">
  <nav id="navbar" class="navbar fixed-top  navbar-expand-lg navbar-dark scrolling-navbar">
  <div class="container">
    <a class="navbar-brand" href="/">
      <strong>有追求的开发者</strong>
    </a>

    <button id="navbar-toggler-btn" class="navbar-toggler" type="button" data-toggle="collapse"
            data-target="#navbarSupportedContent"
            aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
      <div class="animated-icon"><span></span><span></span><span></span></div>
    </button>

    <!-- Collapsible content -->
    <div class="collapse navbar-collapse" id="navbarSupportedContent">
      <ul class="navbar-nav ml-auto text-center">
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/" target="_self">
                <i class="iconfont icon-home-fill"></i>
                <span>首页</span>
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/archives/" target="_self">
                <i class="iconfont icon-archive-fill"></i>
                <span>归档</span>
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/categories/" target="_self">
                <i class="iconfont icon-category-fill"></i>
                <span>分类</span>
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/tags/" target="_self">
                <i class="iconfont icon-tags-fill"></i>
                <span>标签</span>
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/about/" target="_self">
                <i class="iconfont icon-user-fill"></i>
                <span>关于</span>
              </a>
            </li>
          
        
        
          <li class="nav-item" id="search-btn">
            <a class="nav-link" target="_self" href="javascript:;" data-toggle="modal" data-target="#modalSearch" aria-label="Search">
              <i class="iconfont icon-search"></i>
            </a>
          </li>
          
        
        
          <li class="nav-item" id="color-toggle-btn">
            <a class="nav-link" target="_self" href="javascript:;" aria-label="Color Toggle">
              <i class="iconfont icon-dark" id="color-toggle-icon"></i>
            </a>
          </li>
        
      </ul>
    </div>
  </div>
</nav>

  

<div id="banner" class="banner" parallax=true
     style="background: url('/img/bg/big.jpg') no-repeat center center; background-size: cover;">
  <div class="full-bg-img">
    <div class="mask flex-center" style="background-color: rgba(0, 0, 0, 0.3)">
      <div class="banner-text text-center fade-in-up">
        <div class="h2">
          
            <span id="subtitle" data-typed-text="消息队列选型大战：如何在性能与可靠性之间找到最优解"></span>
          
        </div>

        
          
  <div class="mt-3">
    
    
      <span class="post-meta">
        <i class="iconfont icon-date-fill" aria-hidden="true"></i>
        <time datetime="2025-02-11 11:06" pubdate>
          2025年2月11日 上午
        </time>
      </span>
    
  </div>

  <div class="mt-1">
    
      <span class="post-meta mr-2">
        <i class="iconfont icon-chart"></i>
        
          5.9k 字
        
      </span>
    

    
      <span class="post-meta mr-2">
        <i class="iconfont icon-clock-fill"></i>
        
        
        
          50 分钟
        
      </span>
    

    
    
  </div>


        
      </div>

      
    </div>
  </div>
</div>

</div>

  </header>

  <main>
    
      

<div class="container-fluid nopadding-x">
  <div class="row nomargin-x">
    <div class="side-col d-none d-lg-block col-lg-2">
      

    </div>

    <div class="col-lg-8 nopadding-x-md">
      <div class="container nopadding-x-md" id="board-ctn">
        <div id="board">
          <article class="post-content mx-auto">
            <h1 id="seo-header">消息队列选型大战：如何在性能与可靠性之间找到最优解</h1>
            
            
              <div class="markdown-body">
                
                <blockquote>
<p>消息队列是重要的分布式系统组件，在高性能、高可用、低耦合等系统架构中扮演着重要作用。可用于异步通信、削峰填谷、解耦系统、数据缓存等多种业务场景。本文是关于消息队列（MQ）选型和常见问题的精心整理。在这篇文章中，我们将详细介绍消息队列的概念、作用以及如何选择适合自己需求的消息队列系统。</p>
</blockquote>
<h1 id="1、概述"><a href="#1、概述" class="headerlink" title="1、概述"></a>1、概述</h1><p>消息队列在分布式系统中扮演着至关重要的角色，它为系统架构提供了高性能、高可用性和低耦合的特性。通过利用消息队列的能力，分布式系统可以轻松实现以下功能：</p>
<ol>
<li>解耦：将一个流程的上下游拆解开，上游专注于生产消息，下游专注于处理消息；</li>
<li>广播：上游生产的消息可以轻松被多个下游服务处理；</li>
<li>缓冲：应对突发流量，消息队列扮演缓冲器的作用，保护下游服务，使其可以根据自身的实际消费能力处理消息；</li>
<li>异步：上游发送消息后可以马上返回，下游可以异步处理消息；</li>
<li>冗余：保留历史消息，处理失败或当出现异常时可以进行重试或者回溯，防止丢失；</li>
</ol>
<h1 id="2、常见消息队列"><a href="#2、常见消息队列" class="headerlink" title="2、常见消息队列"></a>2、常见消息队列</h1><ul>
<li>Kafka</li>
<li>RabbitMQ</li>
</ul>
<h1 id="Kafka"><a href="#Kafka" class="headerlink" title="Kafka"></a>Kafka</h1><h2 id="Kafaka-系统框架"><a href="#Kafaka-系统框架" class="headerlink" title="Kafaka 系统框架"></a>Kafaka 系统框架</h2><p><img src="https://files.mdnice.com/user/33721/bc3f86d9-adac-46a2-ac2f-11a9c3d5ef40.png" srcset="/img/loading.gif" lazyload alt="image"></p>
<p>一个 Kafka 集群由多个 Broker 和一个 ZooKeeper 集群构成，其中 Broker 是 Kafka 节点的服务器。一个消息主题 Topic 可以由多个分区 Partition 组成，这些分区物理存储在 Broker 上。为了负载均衡，同一个 Topic 的多个分区存储在多个不同的 Broker 上。为了提高可靠性，每个分区在不同的 Broker 上会存在副本。ZooKeeper 是一个分布式开源的应用程序协调服务，可以实现统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等工作。在 Kafka 中，ZooKeeper 主要有以下几个作用：</p>
<ul>
<li><p>注册 Broker 的过程能够确保集群在某个 Broker 出现故障时迅速获得通知。</p>
</li>
<li><p>对于 Topic 的注册，它涉及到跟踪每个分区的副本所在的 Broker 节点，并记录它们作为 leader 或 follower 的身份。</p>
</li>
<li><p>至于 Consumer 的注册，它负责记录消费者组的偏移量，并管理消费者与分区的对应关系，以此来平衡消费负载。</p>
</li>
</ul>
<h2 id="Kafka-基本术语"><a href="#Kafka-基本术语" class="headerlink" title="Kafka 基本术语"></a>Kafka 基本术语</h2><p><strong>Producer</strong>：消息生产者。通常，消息会被发送至特定的主题。默认情况下，生产者通过轮询机制将消息均衡写入不同分区。生产者也可以根据消息的 key 值来指定写入特定分区。分区的数据分布越均匀，Kafka 的性能表现越佳。</p>
<p><strong>Topic</strong>：Topic 是一个抽象概念，代表一类消息的集合。在一个 Kafka 集群中，可以有多个 Topic。生产者将消息发布到 Topic，消费者通过订阅 Topic 来接收消息。</p>
<p><strong>Partition</strong>：Partition 是一个物理存储单位，每个 Topic 可以包含一个或多个 Partition。新消息以追加的方式存储在分区中，且在同一个 Partition 内部，消息保持有序。通过分区，Kafka 实现了消息的冗余、伸缩性以及并发的读写能力，显著提升了系统的吞吐量。</p>
<p><strong>Replicas</strong>：每个 Partition 都有多个 Replicas（副本）。这些副本分布在不同的 Broker 上。每个 Broker 可能存储成百上千个不同 Topic 和 Partition 的副本。副本分为两种：master 副本（Leader），每个 Partition 有一个 master 副本，所有读写操作都通过 master 副本进行；follower 副本（Follower）仅同步 master 副本的内容，不处理客户端请求。如果 master 副本发生故障，一个 follower 副本将迅速被选举为新的 master。</p>
<p><strong>Consumer</strong>：消息消费者。消费者通过订阅 Topic 来接收消息，并按照一定的顺序消费。Kafka 确保每个分区只能由一个消费者消费。</p>
<p><strong>Offset</strong>：偏移量是一个递增的整数，用于标识消息在分区内的位置。Kafka 在消息写入时会自动添加偏移量。每个分区的偏移量是唯一的。消费者在消费过程中会记录最后处理的偏移量，即使消费者关闭，偏移量也不会丢失，重新启动后可以从上次的位置继续消费。</p>
<p><strong>Broker</strong>：Broker 是独立的 Kafka 服务器。如果一个 Topic 有 N 个 Partition，而集群有 N 个 Broker，那么每个 Broker 将存储该 Topic 的一个 Partition。如果 Topic 有 N 个 Partition，而集群有 N+M 个 Broker，那么 N 个 Broker 存储该 Topic 的 Partition，剩下的 M 个 Broker 不存储。如果 Topic 的 Partition 数量超过 Broker 数量，可能会导致数据不均衡，这种情况在实际生产中应尽量避免。</p>
<h1 id="RabbitMQ"><a href="#RabbitMQ" class="headerlink" title="RabbitMQ"></a>RabbitMQ</h1><h2 id="RabbitMQ-系统框架"><a href="#RabbitMQ-系统框架" class="headerlink" title="RabbitMQ 系统框架"></a>RabbitMQ 系统框架</h2><p><img src="https://files.mdnice.com/user/33721/97a90923-4fb0-48c2-affb-be5d90d4097d.png" srcset="/img/loading.gif" lazyload alt="image"></p>
<p>RabbitMQ 基于 AMQP 协议来实现，主要由 Exchange 和 Queue 两部分组成，然后通过 RoutingKey 关联起来，消息投递到 Exchange 然后通过 Queue 接收。</p>
<h2 id="RabbitMQ-基本术语"><a href="#RabbitMQ-基本术语" class="headerlink" title="RabbitMQ 基本术语"></a>RabbitMQ 基本术语</h2><p><strong>经纪人（Broker）</strong>：负责接纳客户端的连接，并提供 AMQP 消息队列的管理以及路由功能。</p>
<p><strong>虚拟主机（Virtual Host）</strong>：作为一种抽象的实体，它是进行权限管理的最小单元。每个虚拟主机内可以包含多个交换机和队列。</p>
<p><strong>交换机（Exchange）</strong>：</p>
<ul>
<li>负责接收来自消息生产者的消息。</li>
<li>根据不同的交换机类型（ExchangeType）将消息转发到相应的队列。</li>
<li>常见的交换机类型包括直接（direct）、广播（fanout）和主题（topic）。</li>
</ul>
<p><strong>消息队列（Message Queue）</strong>：</p>
<ul>
<li>用于存储待消费的消息。</li>
</ul>
<p><strong>消息（Message）</strong>：</p>
<ul>
<li>由头部（Header）和主体（Body）两部分构成。</li>
<li>头部包含生产者设置的各种属性，如消息的持久化、目标队列、优先级等。</li>
<li>主体包含消息的具体内容。</li>
</ul>
<p><strong>绑定（Binding）</strong>：</p>
<ul>
<li>实现交换机与消息队列之间的连接。</li>
<li>服务器运行时，会创建路由表，记录消息队列的条件和绑定键（BindingKey）。</li>
<li>交换机收到消息后，会根据消息头部的 BindingKey 和路由表，以及交换机类型，将消息路由至正确的消息队列。</li>
</ul>
<p><strong>连接（Connection）</strong>：</p>
<ul>
<li>指的是经纪人与客户端之间的 TCP 网络连接。</li>
</ul>
<p><strong>通道（Channel）</strong>：</p>
<ul>
<li>为了传输消息，需要在连接上创建信道。</li>
<li>根据 AMQP 协议，只有通过信道才能执行 AMQP 命令。</li>
<li>一个连接可以包含多个信道。</li>
<li>建立信道的目的是为了节约资源，避免每个客户端或线程都维护一个 TCP 连接。</li>
<li>通常建议共享连接，但 RabbitMQ 建议不同客户端线程不要共享信道，以确保消息的串行发送。</li>
</ul>
<p><strong>命令（Command）</strong>：</p>
<ul>
<li>客户端通过发送 AMQP 命令与 AMQP 服务器进行交互。</li>
</ul>
<h1 id="3、选型要点"><a href="#3、选型要点" class="headerlink" title="3、选型要点"></a>3、选型要点</h1><h2 id="3-1-选择标准要点"><a href="#3-1-选择标准要点" class="headerlink" title="3.1 选择标准要点"></a>3.1 选择标准要点</h2><ol>
<li>消息顺序性：确认消息队列是否能够保证消息在消费时的顺序与发送时一致。</li>
<li>可伸缩性：<ul>
<li>扩容能力：在面对性能瓶颈，如消息消费速度慢时，系统是否能够迅速进行扩容。</li>
<li>缩容能力：当消费队列数量过多，造成资源浪费时，系统是否支持减少资源使用进行缩容。</li>
</ul>
</li>
<li>消息保留：检查消息在成功消费后，是否还能在消息队列中继续保留。</li>
<li>容错机制：确保当消息消费失败时，存在机制能够保证消息最终能够成功消费，例如确保异步退款消息的准确消费。</li>
<li>消息可靠性：评估是否存在消息丢失的风险，例如在两个消息中，只有一条消息被消费，另一条消息丢失。</li>
<li>消息时序特性：<ul>
<li>消息存活时间：考虑消息在队列中的最长存活时间。</li>
<li>延迟消息：考察消息队列是否支持延迟发送功能。</li>
</ul>
</li>
<li>吞吐量：确定消息队列能够支持的最大并发处理数量。</li>
<li>消息路由订阅：根据设定规则，消费者是否只能订阅符合特定路由规则的消息，例如消费者可以选择仅订阅某一类消息，而忽略其他类型。</li>
</ol>
<h2 id="3-2、选型对比"><a href="#3-2、选型对比" class="headerlink" title="3.2、选型对比"></a>3.2、选型对比</h2><p><img src="https://files.mdnice.com/user/33721/d15ef861-a9a8-4fd6-afa9-f6f6e7993ac1.png" srcset="/img/loading.gif" lazyload alt="d09d856baedc9bffa6061bdb3675be33"></p>
<h1 id="4、功能剖析"><a href="#4、功能剖析" class="headerlink" title="4、功能剖析"></a>4、功能剖析</h1><h2 id="4-1-消费模式差异"><a href="#4-1-消费模式差异" class="headerlink" title="4.1 消费模式差异"></a>4.1 消费模式差异</h2><p>在消息队列领域，客户端获取消息的方式主要分为两种：拉取（Pull）和推送（Push）。Kafka 和 RocketMQ 采用长轮询的拉取方式来获取消息，而 RabbitMQ、Pulsar 和 NSQ 则使用推送方式。</p>
<p>拉取方式的消息队列，如 Kafka 和 RocketMQ，更适合于需要高吞吐量的应用场景。这种方式允许消费者根据自身的处理能力来控制消息的接收速度，从而实现流量的自我调节。</p>
<p>相比之下，推送方式的消息队列，例如 RabbitMQ、Pulsar 和 NSQ，提供了更好的实时性。但是，为了应对消费者处理能力不足的情况，它们需要具备有效的流量控制机制（例如 backpressure）。这种机制能够在消费者负载过高时，自动减少消息的推送频率，以防止消费者端系统过载。</p>
<h2 id="4-2、延迟队列"><a href="#4-2、延迟队列" class="headerlink" title="4.2、延迟队列"></a>4.2、延迟队列</h2><p>消息的延迟投递功能允许在某些业务需求中，消费者不必立即接收到消息，而是在指定的延时之后才能获取并处理该消息。这种延迟投递通常分为两种类型：基于单条消息的延迟和基于整个队列的延迟。</p>
<p>基于单条消息的延迟投递是指为每条消息单独设置一个延迟时间。当新消息进入队列时，系统会根据这些延迟时间对消息进行排序。然而，这种方法可能会对系统性能产生较大的影响，因为需要不断地对消息进行排序处理。</p>
<p>另一种方法是基于队列的延迟投递，这种方法为整个队列设置统一的延迟级别。在这种模式下，队列中的所有消息都共享相同的延迟时间。这样做的好处是避免了因排序延迟时间而造成的性能损耗，系统可以通过定期扫描队列来投递那些已经达到延迟时间的消息。</p>
<p>延迟消息的使用场景比如异常检测重试，订单超时取消等，例如：</p>
<ul>
<li>服务请求异常，需要将异常请求放到单独的队列，隔 5 分钟后进行重试；</li>
<li>用户购买商品，但一直处于未支付状态，需要定期提醒用户支付，超时则关闭订单；</li>
<li>面试或者会议预约，在面试或者会议开始前半小时，发送通知再次提醒。</li>
</ul>
<p><strong>Kafka</strong> 目前不提供延迟消息的功能。而 Pulsar 则支持以秒为单位的延迟消息功能。在 Pulsar 中，所有需要延迟投递的消息都会被 Delayed Message Tracker 记录其索引位置。</p>
<p>当消费者准备消费消息时，它会首先查询 Delayed Message Tracker，检查是否有已经达到延迟时间的消息需要投递。如果存在已到期的消息，消费者会根据 Tracker 提供的索引信息，从消息队列中提取相应的消息进行处理。如果所有延迟消息都还未到期，则消费者将继续处理常规消息。</p>
<p>对于需要长时间延迟的消息，Pulsar 会将这些消息存储在磁盘上，以减少内存占用。当这些消息的延迟时间即将结束时，系统会将它们预先加载到内存中，以便及时进行投递。</p>
<p><img src="https://files.mdnice.com/user/33721/919d003a-3c83-4002-9f0a-8a2376bd2719.png" srcset="/img/loading.gif" lazyload alt="image"></p>
<p>RabbitMQ 本身不自带延迟消息功能，但可以通过安装名为 <code>rabbitmq_delayed_message_exchange</code> 的插件来实现延迟消息投递。安装此插件后，RabbitMQ 将支持以下功能：</p>
<ol>
<li><strong>延迟交换</strong>：创建一个特殊的交换器，用于处理带有延迟属性的消息。</li>
<li><strong>消息延迟</strong>：允许用户为消息设置一个延迟时间，消息在这个时间内不会被投递给消费者。</li>
<li><strong>到期处理</strong>：当消息的延迟时间到期后，RabbitMQ 会自动将消息从延迟队列移动到正常的交换器和队列中，然后投递给消费者。</li>
</ol>
<p>安装插件后，用户可以利用这个特性来实现业务场景中对延迟消息的需求。例如，可以用于订单超时未支付自动取消、定时任务调度等场景。</p>
<h2 id="4-3-死信队列"><a href="#4-3-死信队列" class="headerlink" title="4.3 死信队列"></a>4.3 死信队列</h2><p><img src="https://files.mdnice.com/user/33721/cf0bfed8-7422-4194-a8b7-8f347255722e.png" srcset="/img/loading.gif" lazyload alt="image"></p>
<p>当消息因为某些原因无法成功投递时，为防止消息丢失，通常会将其放入一个特定的队列中，这个队列被称为死信队列（DLQ）。与此同时，还有一个与之相关的“回退队列”概念。</p>
<p>设想一下，如果消费者在处理消息时遇到异常，它将不会对这次消费进行确认（Ack）。这意味着消息处理失败，并且消息会被重新放回队列的前端，导致消息不断被重新处理和回滚，从而形成无限循环。</p>
<p>为了避免这种情况，可以为每个队列设置一个回退队列。回退队列和死信队列一样，都是为了处理异常情况而设计的机制。在实际操作中，回退队列的功能通常由死信队列和重试队列共同承担。</p>
<ul>
<li><strong>死信队列</strong>：用于存储那些无法正常处理的消息，通常是因为消息过期或无法被消费。</li>
<li><strong>重试队列</strong>：用于存储那些因为消费者处理异常而需要重新处理的消息。</li>
</ul>
<p>通过这样的设计，可以确保消息不会因为消费者的错误而被无限期地阻塞在队列中，同时也为消息的最终处理提供了保障。</p>
<p><strong>Kafka</strong> 没有死信队列，通过 Offset 的方式记录当前消费的偏移量。<br><strong>RabbitMQ</strong> 是利用类似于延迟队列的形式实现死信队列。</p>
<h2 id="4-4、优先级队列"><a href="#4-4、优先级队列" class="headerlink" title="4.4、优先级队列"></a>4.4、优先级队列</h2><p>有一些业务场景下，我们需要优先处理一些消息，比如银行里面的金卡客户、银卡客户优先级高于普通客户，他们的业务需要优先处理。如下图：</p>
<p><img src="https://files.mdnice.com/user/33721/a25caf99-64f2-40ff-a0f6-c3de925a24d9.png" srcset="/img/loading.gif" lazyload alt="image"></p>
<p>优先级队列与标准的先进先出（FIFO）队列不同，它允许具有更高优先级的消息优先被消费者处理，从而为不同重要性的消息提供差异化的服务保障。然而，这种优先级机制的有效性有一个前提条件：</p>
<p>如果消费者处理消息的速度超过了生产者发送消息的速度，并且消息中间件服务器（通常简称为 Broker）中没有积压的消息，那么为消息设置优先级实际上就没有太大的意义。这是因为生产者一旦发送了一条消息，它几乎立刻就会被消费者处理掉。在这种情况下，Broker 中几乎总是只有一条消息在等待处理，对于这样单一的消息，优先级的概念变得无关紧要。</p>
<p>因此，优先级队列在消息处理速度远低于生产速度，或者 Broker 中存在消息积压的情况下更为有效。在这些情况下，优先级可以帮助确保更重要的消息得到及时处理，而不是简单地按照它们到达的顺序。</p>
<p><strong>Kafka</strong>不支持优先级队列，可以通过不同的队列来实现消息优先级。<br>**RabbitMQ **支持优先级消息。</p>
<h2 id="4-5、消息回溯"><a href="#4-5、消息回溯" class="headerlink" title="4.5、消息回溯"></a>4.5、消息回溯</h2><p>通常情况下，一条消息在被消费者处理之后，就会被标记为已消费，消费者无法再次获取到这条消息。然而，消息回溯功能提供了一种机制，使得消费者在处理完消息之后，仍然能够重新访问并消费之前已经消费过的消息。</p>
<p>消息丢失是一个常见问题，其原因可能是消息中间件的缺陷，也可能是使用方的错误操作，通常难以确定。如果消息中间件支持消息回溯功能，那么可以通过回溯消费来重现“丢失”的消息，从而帮助定位问题的根源。</p>
<p>消息回溯的应用不仅限于解决消息丢失的问题。它还可以用于：</p>
<ul>
<li><strong>索引恢复</strong>：在索引损坏或需要重建的情况下，通过回溯可以重新构建索引。</li>
<li><strong>本地缓存重建</strong>：当本地缓存失效时，可以通过回溯消费来恢复缓存数据。</li>
<li><strong>业务补偿</strong>：在某些业务流程中，如果需要对之前的操作进行补偿或修正，可以通过回溯消费来实现。</li>
</ul>
<p>总的来说，消息回溯功能为消息处理提供了灵活性和容错能力，有助于提高系统的稳定性和可靠性。</p>
<p><strong>Kafka</strong> 支持消息回溯，可以根据时间戳或指定 Offset，重置 Consumer 的 Offset 使其可以重复消费。</p>
<p><strong>RabbitMQ</strong> 不支持回溯，消息一旦标记确认就会被标记删除。</p>
<h2 id="4-6、消息持久化"><a href="#4-6、消息持久化" class="headerlink" title="4.6、消息持久化"></a>4.6、消息持久化</h2><p>流量削峰是消息队列系统核心功能之一，它主要依赖于消息队列的缓冲能力。可以说，如果一个消息队列系统没有能力缓冲消息，那么它就无法被视为一个成熟的解决方案。消息缓冲可以分为两种类型：内存缓冲和磁盘缓冲。</p>
<p>通常，磁盘的存储容量远大于内存，因此，采用磁盘缓冲时，其缓冲能力理论上可以等同于整个磁盘的容量。此外，消息缓冲还为消息队列系统提供了数据冗余存储的优势。</p>
<ul>
<li><strong>内存缓冲</strong>：提供快速的消息处理能力，但受限于物理内存的大小。</li>
<li><strong>磁盘缓冲</strong>：提供更大的存储空间，适用于处理大规模数据，但可能在处理速度上不如内存缓冲。</li>
</ul>
<p>消息缓冲不仅有助于平滑流量高峰，减少系统压力，还能在出现故障时提供数据恢复的能力，确保消息的持久性和系统的可靠性。</p>
<p><strong>Kafka</strong>直接将消息刷入磁盘文件中进行持久化，所有的消息都存储在磁盘中。只要磁盘容量够，可以做到无限消息堆积。</p>
<p><strong>RabbitMQ</strong> 是典型的内存式堆积，但这并非绝对，在某些条件触发后会有换页动作来将内存中的消息换页到磁盘（换页动作会影响吞吐），或者直接使用惰性队列来将消息直接持久化至磁盘中。</p>
<h2 id="4-7、消息确认机制"><a href="#4-7、消息确认机制" class="headerlink" title="4.7、消息确认机制"></a>4.7、消息确认机制</h2><p>消息队列系统必须跟踪消费者的消费状态，以确保每条消息都被正确处理。在使用推送（push）模式的消息队列中，通常采用对单条消息进行确认的机制。</p>
<p>当消费者成功处理了一条消息后，它会向消息队列发送一个确认信号。如果消费者未能成功处理消息，或者在处理过程中遇到问题而未能发送确认信号，消息队列将认为该消息未被确认。</p>
<p><img src="https://files.mdnice.com/user/33721/55764f74-d6fc-4c45-83ee-2f2d6e861226.jpeg" srcset="/img/loading.gif" lazyload alt="212adc65abb5b61d2fdbdb372e5402b3"></p>
<p>对于这些未确认的消息，消息队列会采取以下措施之一：</p>
<ol>
<li><strong>延迟重新投递</strong>：消息队列会将未确认的消息暂时搁置，经过一定的延迟时间后再次尝试投递给消费者。</li>
<li><strong>进入死信队列</strong>：如果消息在多次尝试投递后仍未被确认，它将被转移到死信队列。死信队列用于存储无法正常处理的消息，以便进行后续的分析或特殊处理。</li>
</ol>
<p>这种确认机制有助于确保消息的可靠性和系统的健壮性，防止消息在处理过程中丢失或被错误地处理。</p>
<p><strong>Kafka 通过偏移量（Offset）来实现消息的确认机制</strong>。具体来说，它包括以下几个方面：</p>
<ol>
<li><p><strong>发送方确认机制</strong>：</p>
<ul>
<li><code>ack=0</code>：生产者不等待任何确认，直接发送消息，不关心消息是否成功写入。</li>
<li><code>ack=1</code>：生产者在消息成功写入首领分区（Leader Replica）后，会收到一个成功的确认。</li>
<li><code>ack=all</code>：生产者在消息成功写入所有分区副本（ISR）后，才会收到成功的确认。</li>
</ul>
</li>
<li><p><strong>接收方确认机制</strong>：</p>
<ul>
<li>消费者可以通过自动或手动的方式提交偏移量，以确认消息已被处理。</li>
<li>在早期版本的 Kafka 中，偏移量信息是提交给 Zookeeper 的，这会增加 Zookeeper 的负载。</li>
<li>而在更新版本的 Kafka 中，偏移量信息直接提交给 Kafka 服务器自身管理，不再依赖 Zookeeper 来管理偏移量，从而提高了集群的性能和稳定性。</li>
</ul>
</li>
</ol>
<p>通过这种方式，Kafka 能够确保消息的可靠性和系统的高效性。</p>
<p><strong>RabbitMQ 通过以下机制来确保消息的可靠传递</strong>：</p>
<ol>
<li><p><strong>发送方确认机制</strong>：</p>
<ul>
<li>当消息成功投递到所有符合条件的队列时，发送方会收到一个成功的确认。</li>
<li>如果消息和队列都被设置为持久化，那么在消息写入磁盘之后，发送方会收到成功的确认。</li>
<li>RabbitMQ 支持批量确认和异步确认，以提高消息处理的效率。</li>
</ul>
</li>
<li><p><strong>接收方确认机制</strong>：</p>
<ul>
<li>如果 <code>autoAck</code> 设置为 <code>false</code>，则消费者需要显式地确认每一条消息，否则消息不会被认为已处理。</li>
<li>如果 <code>autoAck</code> 设置为 <code>true</code>，则消息在被推送给消费者时自动确认。</li>
</ul>
</li>
</ol>
<p>当 <code>autoAck</code> 为 <code>false</code> 时，RabbitMQ 队列会区分两种类型的消息：</p>
<ul>
<li>等待投递给消费者的消息。</li>
<li>已经投递给消费者但尚未收到确认的消息。</li>
</ul>
<p>如果消费者在没有发送确认信号的情况下断开连接，RabbitMQ 会将未确认的消息重新放入队列中，等待再次投递给原来的消费者或其他消费者。</p>
<p>未确认的消息不会设置过期时间。只要消费者没有断开连接，RabbitMQ 会持续等待确认信号。这意味着消费者可以有很长的处理时间来处理每条消息，确保消息能够被正确处理。</p>
<p>通过这种方式，RabbitMQ 能够灵活地处理各种消息传递场景，同时确保消息的可靠性和系统的稳定性。</p>
<h1 id="那么选择什么呢？"><a href="#那么选择什么呢？" class="headerlink" title="那么选择什么呢？"></a>那么选择什么呢？</h1><p>Kafka和MQ各有优势，开发者应根据实际业务场景和需求进行选择。以下是一些建议：</p>
<ol>
<li>如果您的应用场景需要处理大规模数据，建议选择Kafka。</li>
<li>如果您的应用场景更注重数据可靠性和事务性，可以考虑使用MQ。</li>
<li>在云原生和微服务架构中，Kafka具有更好的适应性。</li>
<li>对于中小型企业，MQ的易用性和功能丰富性可能更适合。</li>
</ol>

                
              </div>
            
            <hr/>
            <div>
              <div class="post-metas my-3">
  
    <div class="post-meta mr-3 d-flex align-items-center">
      <i class="iconfont icon-category"></i>
      

<span class="category-chains">
  
  
    
      <span class="category-chain">
        
  <a href="/categories/%E6%A1%86%E6%9E%B6/" class="category-chain-item">框架</a>
  
  

      </span>
    
  
</span>

    </div>
  
  
    <div class="post-meta">
      <i class="iconfont icon-tags"></i>
      
        <a href="/tags/%E6%A1%86%E6%9E%B6/" class="print-no-link">#框架</a>
      
    </div>
  
</div>


              
  

  <div class="license-box my-3">
    <div class="license-title">
      <div>消息队列选型大战：如何在性能与可靠性之间找到最优解</div>
      <div>http://blog.xhweng.cn/2025/02/11/如何选择合适的消息队列/</div>
    </div>
    <div class="license-meta">
      
        <div class="license-meta-item">
          <div>作者</div>
          <div>xuehaoweng</div>
        </div>
      
      
        <div class="license-meta-item license-meta-date">
          <div>发布于</div>
          <div>2025年2月11日</div>
        </div>
      
      
      
        <div class="license-meta-item">
          <div>许可协议</div>
          <div>
            
              
              
                <a class="print-no-link" target="_blank" href="https://creativecommons.org/licenses/by/4.0/">
                  <span class="hint--top hint--rounded" aria-label="BY - 署名">
                    <i class="iconfont icon-by"></i>
                  </span>
                </a>
              
            
          </div>
        </div>
      
    </div>
    <div class="license-icon iconfont"></div>
  </div>



              
                <div class="post-prevnext my-3">
                  <article class="post-prev col-6">
                    
                    
                      <a href="/2025/02/11/nacospythonsdk%E4%BB%8B%E7%BB%8D/" title="Python微服务中Nacos的颠覆性应用：构建配置中心与实时监听，竟隐藏着这样的性能优化秘密！">
                        <i class="iconfont icon-arrowleft"></i>
                        <span class="hidden-mobile">Python微服务中Nacos的颠覆性应用：构建配置中心与实时监听，竟隐藏着这样的性能优化秘密！</span>
                        <span class="visible-mobile">上一篇</span>
                      </a>
                    
                  </article>
                  <article class="post-next col-6">
                    
                    
                      <a href="/2025/02/11/Redis%EF%BC%9A%E9%87%8A%E6%94%BE%E9%97%AA%E7%94%B5%E8%88%AC%E5%BF%AB%E9%80%9F%E7%9A%84%E6%95%B0%E6%8D%AE%E5%AD%98%E5%82%A8%E5%92%8C%E6%A3%80%E7%B4%A2%E8%83%BD%E5%8A%9B/" title="Redis：释放闪电般快速的数据存储和检索能力">
                        <span class="hidden-mobile">Redis：释放闪电般快速的数据存储和检索能力</span>
                        <span class="visible-mobile">下一篇</span>
                        <i class="iconfont icon-arrowright"></i>
                      </a>
                    
                  </article>
                </div>
              
            </div>

            
          </article>
        </div>
      </div>
    </div>

    <div class="side-col d-none d-lg-block col-lg-2">
      
  <aside class="sidebar" style="margin-left: -1rem">
    <div id="toc">
  <p class="toc-header">
    <i class="iconfont icon-list"></i>
    <span>目录</span>
  </p>
  <div class="toc-body" id="toc-body"></div>
</div>



  </aside>


    </div>
  </div>
</div>





  



  



  



  



  







    

    
      <a id="scroll-top-button" aria-label="TOP" href="#" role="button">
        <i class="iconfont icon-arrowup" aria-hidden="true"></i>
      </a>
    

    
      <div class="modal fade" id="modalSearch" tabindex="-1" role="dialog" aria-labelledby="ModalLabel"
     aria-hidden="true">
  <div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
    <div class="modal-content">
      <div class="modal-header text-center">
        <h4 class="modal-title w-100 font-weight-bold">搜索</h4>
        <button type="button" id="local-search-close" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body mx-3">
        <div class="md-form mb-5">
          <input type="text" id="local-search-input" class="form-control validate">
          <label data-error="x" data-success="v" for="local-search-input">关键词</label>
        </div>
        <div class="list-group" id="local-search-result"></div>
      </div>
    </div>
  </div>
</div>

    

    
  </main>

  <footer>
    <div class="footer-inner">
  
    <div class="footer-content">
       <a href="https://hexo.io" target="_blank" rel="nofollow noopener"><span>Hexo</span></a> <i class="iconfont icon-love"></i> <a href="https://github.com/fluid-dev/hexo-theme-fluid" target="_blank" rel="nofollow noopener"><span>Fluid</span></a> 
    </div>
  
  
    <div class="statistics">
  
  

  
    
      <span id="busuanzi_container_site_pv" style="display: none">
        总访问量 
        <span id="busuanzi_value_site_pv"></span>
         次
      </span>
    
    
      <span id="busuanzi_container_site_uv" style="display: none">
        总访客数 
        <span id="busuanzi_value_site_uv"></span>
         人
      </span>
    
    
  
</div>

  
  
    <!-- 备案信息 ICP for China -->
    <div class="beian">
  <span>
    <a href="http://beian.miit.gov.cn/" target="_blank" rel="nofollow noopener">
      皖ICP备2024038809号-2
    </a>
  </span>
  
    
      <span>
        <a
          href="http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=12345678"
          rel="nofollow noopener"
          class="beian-police"
          target="_blank"
        >
          
            <span style="visibility: hidden; width: 0">|</span>
            <img src="/img/police_beian.png" srcset="/img/loading.gif" lazyload alt="police-icon"/>
          
          <span>京公网安备12345678号</span>
        </a>
      </span>
    
  
</div>

  
  
</div>

  </footer>

  <!-- Scripts -->
  
  <script  src="https://lib.baomitu.com/nprogress/0.2.0/nprogress.min.js" ></script>
  <link  rel="stylesheet" href="https://lib.baomitu.com/nprogress/0.2.0/nprogress.min.css" />

  <script>
    NProgress.configure({"showSpinner":false,"trickleSpeed":100})
    NProgress.start()
    window.addEventListener('load', function() {
      NProgress.done();
    })
  </script>


<script  src="https://lib.baomitu.com/jquery/3.6.4/jquery.min.js" ></script>
<script  src="https://lib.baomitu.com/twitter-bootstrap/4.6.1/js/bootstrap.min.js" ></script>
<script  src="/js/events.js" ></script>
<script  src="/js/plugins.js" ></script>


  <script  src="https://lib.baomitu.com/typed.js/2.0.12/typed.min.js" ></script>
  <script>
    (function (window, document) {
      var typing = Fluid.plugins.typing;
      var subtitle = document.getElementById('subtitle');
      if (!subtitle || !typing) {
        return;
      }
      var text = subtitle.getAttribute('data-typed-text');
      
        typing(text);
      
    })(window, document);
  </script>




  
    <script  src="/js/img-lazyload.js" ></script>
  




  
<script>
  Fluid.utils.createScript('https://lib.baomitu.com/tocbot/4.20.1/tocbot.min.js', function() {
    var toc = jQuery('#toc');
    if (toc.length === 0 || !window.tocbot) { return; }
    var boardCtn = jQuery('#board-ctn');
    var boardTop = boardCtn.offset().top;

    window.tocbot.init(Object.assign({
      tocSelector     : '#toc-body',
      contentSelector : '.markdown-body',
      linkClass       : 'tocbot-link',
      activeLinkClass : 'tocbot-active-link',
      listClass       : 'tocbot-list',
      isCollapsedClass: 'tocbot-is-collapsed',
      collapsibleClass: 'tocbot-is-collapsible',
      scrollSmooth    : true,
      includeTitleTags: true,
      headingsOffset  : -boardTop,
    }, CONFIG.toc));
    if (toc.find('.toc-list-item').length > 0) {
      toc.css('visibility', 'visible');
    }

    Fluid.events.registerRefreshCallback(function() {
      if ('tocbot' in window) {
        tocbot.refresh();
        var toc = jQuery('#toc');
        if (toc.length === 0 || !tocbot) {
          return;
        }
        if (toc.find('.toc-list-item').length > 0) {
          toc.css('visibility', 'visible');
        }
      }
    });
  });
</script>


  <script src=https://lib.baomitu.com/clipboard.js/2.0.11/clipboard.min.js></script>

  <script>Fluid.plugins.codeWidget();</script>


  
<script>
  Fluid.utils.createScript('https://lib.baomitu.com/anchor-js/4.3.1/anchor.min.js', function() {
    window.anchors.options = {
      placement: CONFIG.anchorjs.placement,
      visible  : CONFIG.anchorjs.visible
    };
    if (CONFIG.anchorjs.icon) {
      window.anchors.options.icon = CONFIG.anchorjs.icon;
    }
    var el = (CONFIG.anchorjs.element || 'h1,h2,h3,h4,h5,h6').split(',');
    var res = [];
    for (var item of el) {
      res.push('.markdown-body > ' + item.trim());
    }
    if (CONFIG.anchorjs.placement === 'left') {
      window.anchors.options.class = 'anchorjs-link-left';
    }
    window.anchors.add(res.join(', '));

    Fluid.events.registerRefreshCallback(function() {
      if ('anchors' in window) {
        anchors.removeAll();
        var el = (CONFIG.anchorjs.element || 'h1,h2,h3,h4,h5,h6').split(',');
        var res = [];
        for (var item of el) {
          res.push('.markdown-body > ' + item.trim());
        }
        if (CONFIG.anchorjs.placement === 'left') {
          anchors.options.class = 'anchorjs-link-left';
        }
        anchors.add(res.join(', '));
      }
    });
  });
</script>


  
<script>
  Fluid.utils.createScript('https://lib.baomitu.com/fancybox/3.5.7/jquery.fancybox.min.js', function() {
    Fluid.plugins.fancyBox();
  });
</script>


  <script>Fluid.plugins.imageCaption();</script>

  <script  src="/js/local-search.js" ></script>

  <script defer src="https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js" ></script>





<!-- 主题的启动项，将它保持在最底部 -->
<!-- the boot of the theme, keep it at the bottom -->
<script  src="/js/boot.js" ></script>


  

  <noscript>
    <div class="noscript-warning">博客在允许 JavaScript 运行的环境下浏览效果更佳</div>
  </noscript>
</body>
</html>
